package net.pulseproject.router.config.instantiator;

import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;

import net.pulseproject.commons.util.TypeUtil;
import net.pulseproject.router.config.ModuleFilter;
import net.pulseproject.router.config.instantiator.builder.Builder;
import net.pulseproject.router.config.parser.LineConfig;
import net.pulseproject.router.modules.Module;
import net.pulseproject.router.modules.useable.MidiReceiverModule;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class ModuleInstantiatorImpl implements ModuleInstantiator {
	
	private static final Log LOG = LogFactory.getLog(ModuleInstantiatorImpl.class);

	private static final String BUILDER_PACKAGE_NAME_WITH_DOT =
		"net.pulseproject.router.config.instantiator.builder."; // TODO use some class instance and extract package name dynamically
	
	@Override
	public final Module instantiate(final LineConfig lineConfig, final Map<String, Module> availableModules, 
			final Collection<LineConfig> allConfigs) {
		LOG.debug("instantiate(lineConfig=" + lineConfig + ", availableModules, allConfigs)");
		
		final String id = lineConfig.getId();
		final String type = lineConfig.getType();
		final Collection<String> outgoingIds = lineConfig.getOutgoingIds();
		final Map<String, Object> properties = lineConfig.getProperties();
		
		final Builder<? extends Module> builder = this.createBuilder(type);
		builder.setId(id);
		
		for (final Entry<String, Object> entry : properties.entrySet()) {
			final String propertyName = entry.getKey();
			final Object propertyValue = entry.getValue();
			TypeUtil.invokeSetter(builder, propertyName, propertyValue);
		}
		
		if(type.equals(MidiReceiverModule.class.getSimpleName()) == false) {
			final ModuleFilter filter = new ModuleFilter();
			final Collection<Module> outgoingModules = filter.filterModulesById(outgoingIds, availableModules);
			TypeUtil.invokeSetter(builder, "outgoingModules", outgoingModules);
		}
		
		if(TypeUtil.hasSetterMethod(builder.getClass(), "fanIn")) {
			TypeUtil.invokeSetter(builder, "fanIn", this.calculateFanIn(id, allConfigs));
		}
		
		return builder.build();
	}
	
	// MINOR outsource method
	private Integer calculateFanIn(final String lookupId, final Collection<LineConfig> allConfigs) {
		int fanIn = 0;
		for (final LineConfig currentConfig : allConfigs) {
			if(currentConfig.getOutgoingIds().contains(lookupId) == true) {
				fanIn++;
			}
		}
		return Integer.valueOf(fanIn);
	}
	
	@SuppressWarnings("unchecked")
	private Builder<? extends Module> createBuilder(final String moduleType) {
		final String builderClassName = moduleType + "Builder";
		final String builderFullQualifiedClassName = BUILDER_PACKAGE_NAME_WITH_DOT + builderClassName;
		
		final Class<? extends Builder> builderClass;
		try {
			builderClass = (Class<? extends Builder>) Class.forName(builderFullQualifiedClassName);
		} catch(final Exception e) {
			throw new RuntimeException("Could not find class [" + builderFullQualifiedClassName + "]!");
		}
		
		final Builder<? extends Module> builder;
		try {
			builder = builderClass.newInstance();
		} catch (final InstantiationException e) {
			throw new RuntimeException("Could not instantiate class [" + builderFullQualifiedClassName + "]!");
		} catch (final IllegalAccessException e) {
			throw new RuntimeException("Could not instantiate class [" + builderFullQualifiedClassName + "]!");
		}
		
		return builder;
	}

}
